using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;


namespace GempGame
{
    /// <summary>
    /// This is a game component that implements IUpdateable.
    /// </summary>
    public class Map : Microsoft.Xna.Framework.GameComponent
    {
        public Grid gridMap { get; set; }

        SpriteBatch spriteBatch;
        Texture2D levelTexture;

        public PlayerUI player;
        Building home;
        Building bank;
        Building church;
        Building factory;
        Building office;

        int playerX = 50, playerY = 50;

        int homeX = 100, homeY = 70, homeWidth = 150, homeHeight = 120;
        int bankX = 700, bankY = 85, bankWidth = 150, bankHeight = 120;
        int churchX = 100, churchY = 320, churchWidth = 150, churchHeight = 120;
        int factoryX = 800, factoryY = 400, factoryWidth = 200, factoryHeight = 160;
        int officeX = 500, officeY = 280, officeWidth = 150, officeHeight = 120;

        List<NPC> npcs;
        int npcsMax = 30;
        int visibleNPCs = 0;
        string[] npcSprites = { "npc1_ubberhappy", "npc1_happy", "npc1_sad", "npc2_excited", "npc2_happy", "npc2_sad" };

        public MouseState newState, oldState; //estado do teclado para obter released clicks


        static Random rnd = new Random();


        public Map(Game game)
            : base(game)
        {
            player = new PlayerUI(game, new Vector2(playerX, playerY), 4, 4, "player", 270);

            home = new Building(game, Building.Name.HOME, "home", homeX, homeY, homeWidth, homeHeight);
            bank = new Building(game, Building.Name.BANK, "bank", bankX, bankY, bankWidth, bankHeight);
            church = new Building(game, Building.Name.CHURCH, "church", churchX, churchY, churchWidth, churchHeight);
            factory = new Building(game, Building.Name.FACTORY, "factory", factoryX, factoryY, factoryWidth, factoryHeight);
            office = new Building(game, Building.Name.OFFICE, "office", officeX, officeY, officeWidth, officeHeight);

            gridMap = new Grid(Game.GraphicsDevice.Viewport.Width, Game.GraphicsDevice.Viewport.Height);
            player.tileWidth = gridMap.tileWidth;
            player.tileHeight = gridMap.tileHeight;

            gridMap.addObject(home.buildingRect);
            gridMap.addObject(bank.buildingRect);
            gridMap.addObject(church.buildingRect);
            gridMap.addObject(factory.buildingRect);
            gridMap.addObject(office.buildingRect);


            // adiciona npcsMax bonecos ao mapa
            npcs = new List<NPC>();
            for (int i = 0; i < visibleNPCs; i++)
            {
                int x = rnd.Next(1, Game.GraphicsDevice.Viewport.Width-10);
                int y = rnd.Next(1, Game.GraphicsDevice.Viewport.Height-10);

                npcs.Add(
                    new NPC(
                        game, 
                        new Vector2(x, y),
                        4, 4,
                        npcSprites[rnd.Next(0,5)], 
                        100));

                Console.WriteLine("added npc to "+ x + "/"+ y);
            }
        }

        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            oldState = Mouse.GetState();

            player.Initialize();
            home.Initialize();
            bank.Initialize();
            church.Initialize();
            factory.Initialize();
            office.Initialize();

            //cria lista com os rectangulos dos edificios
            Dictionary<Building.Name, Rectangle> collisionRectangles = new Dictionary<Building.Name, Rectangle>();
            collisionRectangles.Add(home.buildingName, home.buildingRect);
            collisionRectangles.Add(bank.buildingName, bank.buildingRect);
            collisionRectangles.Add(church.buildingName, church.buildingRect);
            collisionRectangles.Add(factory.buildingName, factory.buildingRect);
            collisionRectangles.Add(office.buildingName, office.buildingRect);

            player.collisionRectangles = collisionRectangles;


            //cria lista com os valores do dictionary
            List<Rectangle> aux = new List<Rectangle>();
            foreach (KeyValuePair<Building.Name, Rectangle> pair in collisionRectangles)
            {
                aux.Add(pair.Value);
            }

            //adiciona lista dos rectangulos a cada npc
            foreach (NPC npc in npcs)
            {
                npc.collisionRectangles = aux;
                npc.Initialize();
            }


            base.Initialize();
        }


        public void LoadContent()
        {
            spriteBatch = new SpriteBatch(Game.GraphicsDevice);
            levelTexture = Game.Content.Load<Texture2D>("level");

            player.LoadContent();
            home.LoadContent();
            bank.LoadContent();
            church.LoadContent();
            factory.LoadContent();
            office.LoadContent();

            foreach (NPC npc in npcs)
            {
                npc.LoadContent();
            }
        }

        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            //keyboard stuff
            newState = Mouse.GetState();

            // Se clicar algures no ecra define ponto de destino
            if (player.Updatable && 
                newState.LeftButton == ButtonState.Pressed &&
                oldState.LeftButton == ButtonState.Released)
            {
                player.pointDestination = new Vector2(newState.X, newState.Y);
                player.setOnRoute(player.pointDestination);
                //player.alreadyVisited = false;

                Console.WriteLine("pointDestination: " + player.pointDestination);

                // impede jogador de sair das bordas do ecra
                player.restrictDestination();

                //call the a* algorythm
                player.path = gridMap.findPath(player.location, player.pointDestination);

                Console.WriteLine("the path:");
                foreach (Vector2 v in player.path)
                {
                    Console.Write(v.X + "/" + v.Y + " # ");
                }
                Console.WriteLine();
            }
            oldState = newState;



            player.Update(gameTime);

            home.Update(gameTime);
            bank.Update(gameTime);
            church.Update(gameTime);
            factory.Update(gameTime);
            office.Update(gameTime);

            List<NPC> aux = new List<NPC>(npcs);
            foreach (NPC npc in aux)
            {
                //remove npc se ele sair do ecra
                if (!Game.GraphicsDevice.Viewport.Bounds.Intersects(npc.rectangle))
                {
                    Console.WriteLine("npc out of bounds");
                    npcs.Remove(npc);
                    
                    updateNPCs();
                }
                else
                    npc.Update(gameTime);
            }

            base.Update(gameTime);
        }

        public void Draw(GameTime gameTime)
        {
            // Level background 
            spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);
            spriteBatch.Draw(levelTexture, Game.GraphicsDevice.Viewport.Bounds, new Color(255, 255, 255, 255));
            spriteBatch.End();

            home.Draw(gameTime);
            bank.Draw(gameTime);
            church.Draw(gameTime);
            factory.Draw(gameTime);
            office.Draw(gameTime);

            foreach (NPC npc in npcs)
            {
                npc.Draw(gameTime);
            }

            player.Draw(gameTime);
        }

        // returns the building which the player is colliding. returns Building.Name.NONE, if no collision is detected
        public Building.Name IsPlayerColliding(float totalSeconds)
        {
            return player.collidesWithBuildings(totalSeconds);
        }

        public Building.Name getPlayerOnRoute()
        {
            return player.onRoute;
        }

        public void setEnabledPlayer(bool p)
        {
            this.player.Updatable = p;
        }

        public bool IsEnabledPlayer()
        {
            return this.player.Updatable;
        }

        public bool playerIsDone()
        {
            return (this.player.path.Count == 0);
        }

        public bool alreadyVisited()
        {
            return player.alreadyVisited;
        }
        public void setAlreadyVisited(bool p)
        {
 	        player.alreadyVisited = p;
        }

        public bool isPlayerFinished()
        {
            return player.finished;
        }

        public void setPlayerFinished(bool p)
        {
            player.finished = p;
        }

        public Building.Name getClosestBuilding()
        {
            foreach (KeyValuePair<Building.Name, Rectangle> pair in player.collisionRectangles)
            {
                Rectangle r = new Rectangle(pair.Value.X, pair.Value.Y, pair.Value.Width, pair.Value.Height);
                r.Inflate(2 * gridMap.tileWidth, 2 * gridMap.tileHeight);

                if (r.Intersects(player.rectangle))
                    return pair.Key;
            }

            return Building.Name.NONE;
        }

        public void updateNPCs(double currentPercentage)
        {
            visibleNPCs = (int) (npcsMax * currentPercentage);
            Console.WriteLine("PERCENTAGE: " + currentPercentage);

            updateNPCs();
        }

        public void updateNPCs()
        {
            Console.WriteLine("NPCS.Count: " + npcs.Count);
            Console.WriteLine("VISIBLE_NPCs: " + visibleNPCs);

            if (visibleNPCs == npcs.Count)
                return;
            else if (visibleNPCs > npcs.Count)
            {
                for (int i = npcs.Count; i < visibleNPCs; i++)
                {

                    //adiciona npcs ao ecra
                    int x = rnd.Next(1, Game.GraphicsDevice.Viewport.Width - 10);
                    int y = rnd.Next(1, Game.GraphicsDevice.Viewport.Height - 10);

                    //constructor
                    NPC newNpc =
                        new NPC(
                            Game,
                            new Vector2(x, y),
                            4, 4,
                            npcSprites[rnd.Next(0, 5)],
                            100);


                    //add collision rectangles
                    List<Rectangle> aux = new List<Rectangle>();
                    foreach (KeyValuePair<Building.Name, Rectangle> pair in player.collisionRectangles)
                    {
                        aux.Add(pair.Value);
                    }
                    newNpc.collisionRectangles = aux;

                    //initialize
                    newNpc.Initialize();

                    //load content
                    newNpc.LoadContent();

                    //add to the list
                    npcs.Add(newNpc);

                    Console.WriteLine("added npc to " + x + "/" + y);
                }
            }
            else if (visibleNPCs < npcs.Count)
            {
                //remove npcs em excesso
                for (int i = npcs.Count - 1; i >= visibleNPCs; i--)
                {
                    Console.WriteLine("removing extra npcs");
                    npcs.RemoveAt(i);
                }
            }
            Console.WriteLine("npcs.Count: " + npcs.Count);
        }
    }
}
